import sys
import numpy as np
from PIL import Image
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen, QImage, QPixmap, QFont, QPalette, QBrush
from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QLineEdit, QApplication, QMessageBox, QTableWidget, \
    QTableWidgetItem
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
from math import sqrt
from keras.layers import *
from keras.models import *
from sklearn import preprocessing
from pandas import DataFrame
from pylab import mpl
import sys
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QPalette, QPixmap
from PyQt5.QtWidgets import QWidget, QApplication, QLabel
from PIL import Image
from PyQt5.QtGui import QPalette, QPixmap, QMovie  # 修正这一行
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset

mpl.rcParams['font.sans-serif'] = ['SimHei']  # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False


class PyQtGui(QWidget):

    def __init__(self):
        super(PyQtGui, self).__init__()
        self.resize(1420, 900)  # 界面的大小
        self.setWindowTitle("windows")  # 界面的title
        # 读取 GIF 文件
        Image.open("images/background.jpg").resize((1420, 900)).save("images/background.jpg")
        palette = QPalette()
        palette.setBrush(QPalette.Background, QBrush(QPixmap("images/background.jpg")))
        self.setPalette(palette)

        self.label_title = QLabel(self)
        self.label_title.setText("基于深度学习的光伏发电预测系统")
        self.label_title.setStyleSheet("color:white")
        self.label_title.setFont(QFont("Microsoft YaHei", 20, 50))
        self.label_title.move(430, 30)

        self.label_train = QLabel(self)
        self.label_train.setText("训练部分")
        self.label_train.setStyleSheet("color:white")
        self.label_train.setFont(QFont("Microsoft YaHei", 12, 30))
        self.label_train.move(40, 100)

        self.btn_lstm = QPushButton(self)
        self.btn_lstm.setText("训练LSTM模型")
        self.btn_lstm.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_lstm.move(30, 150)
        self.btn_lstm.setFixedSize(280, 40)
        self.btn_lstm.setStyleSheet("background-color:white")
        self.btn_lstm.clicked.connect(self.train_lstm_model)

        self.btn_cnn_gru = QPushButton(self)
        self.btn_cnn_gru.setText("训练GRU模型")
        self.btn_cnn_gru.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_cnn_gru.move(30, 200)
        self.btn_cnn_gru.setFixedSize(280, 40)
        self.btn_cnn_gru.setStyleSheet("background-color:white")
        self.btn_cnn_gru.clicked.connect(self.train_gru_model)

        self.btn_cnn_gru = QPushButton(self)
        self.btn_cnn_gru.setText("训练CNN-LSTM模型")
        self.btn_cnn_gru.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_cnn_gru.move(30, 250)
        self.btn_cnn_gru.setFixedSize(280, 40)
        self.btn_cnn_gru.setStyleSheet("background-color:white")
        self.btn_cnn_gru.clicked.connect(self.train_cnn_lstm_model)

        self.btn_cnn_gru_attn = QPushButton(self)
        self.btn_cnn_gru_attn.setText("训练CNN-GRU模型")
        self.btn_cnn_gru_attn.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_cnn_gru_attn.move(30, 300)
        self.btn_cnn_gru_attn.setFixedSize(280, 40)
        self.btn_cnn_gru_attn.setStyleSheet("background-color:white")
        self.btn_cnn_gru_attn.clicked.connect(self.train_cnn_gru_model)

        self.btn_cnn_gru_attn = QPushButton(self)
        self.btn_cnn_gru_attn.setText("训练LSTM-Transform模型")
        self.btn_cnn_gru_attn.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_cnn_gru_attn.move(30, 350)
        self.btn_cnn_gru_attn.setFixedSize(280, 40)
        self.btn_cnn_gru_attn.setStyleSheet("background-color:white")
        self.btn_cnn_gru_attn.clicked.connect(self.train_LSTM_transform_model)

        self.label_test = QLabel(self)
        self.label_test.setText("测试部分")
        self.label_test.setStyleSheet("color:white")
        self.label_test.setFont(QFont("Microsoft YaHei", 12, 30))
        self.label_test.move(30, 400)

        self.btn_show_result = QPushButton(self)
        self.btn_show_result.setText("展示不同算法预测对比图")
        self.btn_show_result.setFixedSize(280, 30)
        self.btn_show_result.move(30, 450)
        self.btn_show_result.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_show_result.setStyleSheet("background-color:white")
        self.btn_show_result.clicked.connect(self.show_eval_image)

        self.btn_eval_result = QPushButton(self)
        self.btn_eval_result.setText("展示不同算法性能评价指标")
        self.btn_eval_result.setFixedSize(280, 30)
        self.btn_eval_result.move(30, 500)
        self.btn_eval_result.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_eval_result.setStyleSheet("background-color:white")
        self.btn_eval_result.clicked.connect(self.show_eval_result)

        self.btn_show_datasets = QPushButton(self)
        self.btn_show_datasets.setText("展示数据")
        self.btn_show_datasets.setFixedSize(280, 30)
        self.btn_show_datasets.move(30, 550)
        self.btn_show_datasets.setFont(QFont("Microsoft YaHei", 10, 50))
        self.btn_show_datasets.clicked.connect(self.show_datasets)

        self.label_image = QLabel(self)
        self.label_image.setFixedSize(900, 700)
        self.label_image.move(350, 120)
        self.label_image.setVisible(False)

        self.tableWidget = QTableWidget(self)
        self.tableWidget.setFixedSize(450, 180)
        self.tableWidget.setColumnCount(4)
        self.tableWidget.setRowCount(5)
        self.tableWidget.setHorizontalHeaderLabels(["模型", "MAE", "MSE", "MAPE"])
        self.tableWidget.move(450, 300)
        self.tableWidget.setVisible(False)

        self.dataWidget = QTableWidget(self)
        self.dataWidget.setFixedSize(900, 600)
        self.dataWidget.move(400, 100)
        self.dataWidget.setColumnCount(9)
        self.dataWidget.setRowCount(12097)
        self.dataWidget.setHorizontalHeaderLabels(["date"] + [f"feature{i}" for i in range(1, 8)] + ["光伏产量/KW"])
        self.dataWidget.setVisible(False)

    def show_datasets(self):
        self.dataWidget.setVisible(True)
        self.label_image.setVisible(False)
        self.tableWidget.setVisible(False)
        data = pd.read_excel("datasets/1min.xlsx").values
        for i in range(data.shape[0]):
            for j in range(data.shape[1]):
                self.dataWidget.setItem(i, j, QTableWidgetItem(f"{data[i, j]}"))

    def train_lstm_model(self):
        QMessageBox.question(self, 'YES', '正在训练LSTM模型，训练时间较长请耐心等待!', QMessageBox.Yes | QMessageBox.No,
                             QMessageBox.No)
        window = 6  # 时间窗口大小，用于定义输入数据的序列长度
        lstm_units = 16  # LSTM层的神经元单元数量
        epoch = 100  # 训练迭代次数

        '''
        读取数据
        '''
        # 读取数据文件 'data15.csv'
        df00 = pd.read_excel('datasets/1min.xlsx')
        df1 = df00.iloc[:, 0:10]  # 选择数据中的列范围

        # 打印数据的最后几行
        df1.tail()

        '''
        数据归一化
        '''
        # 使用MinMaxScaler进行数据归一化
        min_max_scaler0 = preprocessing.MinMaxScaler()
        min_max_scaler1 = preprocessing.MinMaxScaler()
        df0 = min_max_scaler0.fit_transform(df1.iloc[:, 0:9])  # 对特征数据归一化
        df2 = min_max_scaler1.fit_transform(df1.iloc[:, 9:10])  # 对被预测产量归一化
        df3 = np.concatenate((df0, df2), axis=1)
        df = pd.DataFrame(df3, columns=df1.columns)
        input_size = len(df.iloc[1, :])

        '''
        构建网络输入
        '''
        stock = df
        seq_len = window
        amount_of_features = len(stock.columns)
        print(amount_of_features)
        data = stock.values
        print(data.shape)
        sequence_length = seq_len + 1
        result = []

        # 创建时间序列数据，每个序列包含 seq_len+1 个数据点
        for index in range(len(data) - sequence_length):
            result.append(data[index: index + sequence_length])
        result = np.array(result)
        print(result.shape)
        row = round(0.5 * result.shape[0])
        print(row)
        train = result[:int(row), :]
        print(train.shape)
        x_train = train[:, :-1]
        y_train = train[:, -1][:, -1]
        print(y_train.shape)
        x_test = result[int(row):, :-1]
        print(x_test.shape)
        y_test = result[int(row):, -1][:, -1]

        '''
        搭建LSTM网络模型
        '''
        # 定义LSTM网络模型
        inputs = Input(shape=(window, input_size))
        model = LSTM(8, activation='tanh', name='LSTM0')(inputs)
        outputs = Dense(1, activation='sigmoid')(model)
        model = Model(inputs=inputs, outputs=outputs)

        # 编译模型，定义损失函数和优化器
        model.compile(loss='mse', optimizer='SGD')

        # 打印模型结构
        model.summary()

        # 训练模型
        history = model.fit(x_train, y_train, 64, epoch, shuffle=False, validation_data=(x_test, y_test))

        # 绘制训练过程中的损失曲线
        loss = history.history['loss']
        plt.plot(loss, label='Train Loss')
        plt.legend(loc='upper right')
        plt.title('Train and Val Loss')
        plt.savefig("images/LSTM-train-loss.png")
        plt.show()

        # 在训练集上的拟合结果
        y_train_predict = model.predict(x_train)
        y_train_predict = y_train_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_train), pd.DataFrame(y_train_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)
        draw.iloc[0:, 0].plot(figsize=(12, 6))
        draw.iloc[0:, 1].plot(figsize=(12, 6))
        plt.legend(('real', 'predict'), fontsize='15')
        plt.title("LSTM Train Data", fontsize='30')
        plt.savefig("images/LSTM-train-result.png")
        plt.show()

        # 在测试集上的预测
        y_test_predict = model.predict(x_test)
        y_test_predict = y_test_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_test), pd.DataFrame(y_test_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)

        # 将测试集的结果保存为 'LSTM.csv' 文件
        draw.to_csv('results/LSTM模型测试集预测与真实结果对比.csv')

        # 绘制测试集的真实数据和预测结果
        fig, ax = plt.subplots(figsize=(10, 7))
        ax.plot(np.array(draw.iloc[:, 0]), label='Data', linestyle='--')
        ax.plot(np.array(draw.iloc[:, 1]), label='Prediction', linestyle='-')

        ax.set_xlabel('时间/分')
        ax.set_ylabel('光伏发电量/Kw')
        plt.title("LSTM Test Data", fontsize='30')
        plt.savefig("images/LSTM-test-result.png")
        plt.show()

        # 输出模型性能指标
        def mape(y_true, y_pred):
            return np.mean(np.abs((y_pred - y_true) / y_true))

        print('训练集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_train_predict, y_train)))  # 平均绝对误差
        print(mean_squared_error(y_train_predict, y_train))  # 均方误差
        print(mape(y_train_predict, y_train))

        print('测试集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_test_predict, y_test)))
        print(mean_squared_error(y_test_predict, y_test))
        print(mape(y_test_predict, y_test))
        # 将评估结果写入文件的第1行，保留四位小数并以百分比形式呈现
        with open('eval_results.txt', 'r+') as file:
            lines = file.readlines()
            mae_test = sqrt(mean_absolute_error(y_test_predict, y_test))
            mse_test = mean_squared_error(y_test_predict, y_test)
            mape_test = mape(y_test_predict, y_test)
            formatted_mae_test = "{:.4f}%".format(mae_test * 100)
            formatted_mse_test = "{:.4f}%".format(mse_test * 100)
            formatted_mape_test = "{:.4f}%".format(mape_test * 100)
            lines.insert(0, f"LSTM {formatted_mae_test} {formatted_mse_test} {formatted_mape_test}\n")
            file.seek(0)
            file.writelines(lines)

    def train_gru_model(self):
        QMessageBox.question(self, 'YES', '正在训练GRU模型，训练时间较长请耐心等待!', QMessageBox.Yes | QMessageBox.No,
                             QMessageBox.No)
        window = 6  # 时间窗口大小，用于定义输入数据的序列长度
        gru_units = 16  # LSTM层的神经元单元数量
        epoch = 100  # 训练迭代次数

        '''
        读取数据
        '''
        # 读取数据文件 'data15.csv'
        df00 = pd.read_excel('datasets/1min.xlsx')
        df1 = df00.iloc[:, 0:10]  # 选择数据中的列范围

        # 打印数据的最后几行
        df1.tail()

        '''
        数据归一化
        '''
        # 使用MinMaxScaler进行数据归一化
        min_max_scaler0 = preprocessing.MinMaxScaler()
        min_max_scaler1 = preprocessing.MinMaxScaler()
        df0 = min_max_scaler0.fit_transform(df1.iloc[:, 0:9])  # 对特征数据归一化
        df2 = min_max_scaler1.fit_transform(df1.iloc[:, 9:10])  # 对被预测流量归一化
        df3 = np.concatenate((df0, df2), axis=1)
        df = pd.DataFrame(df3, columns=df1.columns)
        input_size = len(df.iloc[1, :])

        '''
        构建网络输入
        '''
        stock = df
        seq_len = window
        amount_of_features = len(stock.columns)
        print(amount_of_features)
        data = stock.values
        print(data.shape)
        sequence_length = seq_len + 1
        result = []

        # 创建时间序列数据，每个序列包含 seq_len+1 个数据点
        for index in range(len(data) - sequence_length):
            result.append(data[index: index + sequence_length])
        result = np.array(result)
        print(result.shape)
        row = round(0.5 * result.shape[0])
        print(row)
        train = result[:int(row), :]
        print(train.shape)
        x_train = train[:, :-1]
        y_train = train[:, -1][:, -1]
        print(y_train.shape)
        x_test = result[int(row):, :-1]
        print(x_test.shape)
        y_test = result[int(row):, -1][:, -1]

        '''
        搭建GRU网络模型
        '''
        # 定义GRU网络模型
        inputs = Input(shape=(window, input_size))
        model = GRU(8, activation='tanh', name='GRU')(inputs)
        outputs = Dense(1, activation='sigmoid')(model)
        model = Model(inputs=inputs, outputs=outputs)

        # 编译模型，定义损失函数和优化器
        model.compile(loss='mse', optimizer='SGD')

        # 打印模型结构
        model.summary()

        # 训练模型
        history = model.fit(x_train, y_train, 64, epoch, shuffle=False, validation_data=(x_test, y_test))

        # 绘制训练过程中的损失曲线
        loss = history.history['loss']
        val_loss = history.history['val_loss']
        plt.plot(loss, label='Train Loss')
        plt.legend(loc='upper right')
        plt.title('Train and Val Loss')
        plt.savefig("images/GRU-train-loss.png")
        plt.show()

        # 在训练集上的拟合结果
        y_train_predict = model.predict(x_train)
        y_train_predict = y_train_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_train), pd.DataFrame(y_train_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)
        draw.iloc[0:, 0].plot(figsize=(12, 6))
        draw.iloc[0:, 1].plot(figsize=(12, 6))
        plt.legend(('real', 'predict'), fontsize='15')
        plt.title("GRU Train Data", fontsize='30')
        plt.savefig("images/GRU-train-result.png")
        plt.show()

        # 在测试集上的预测
        y_test_predict = model.predict(x_test)
        y_test_predict = y_test_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_test), pd.DataFrame(y_test_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)

        # 将测试集的结果保存为 'LSTM.csv' 文件
        draw.to_csv('results/GRU模型测试集预测与真实结果对比.csv')
        model.save('model/GRU.h5')

        # 绘制测试集的真实数据和预测结果

        # 对横坐标轴进行标注
        fig, ax = plt.subplots(figsize=(10, 7))
        ax.plot(np.array(draw.iloc[:, 0]), label='Data', linestyle='--')
        ax.plot(np.array(draw.iloc[:, 1]), label='Prediction', linestyle='-')

        ax.set_xlabel('时间/分')
        ax.set_ylabel('光伏发电量/Kw')
        plt.title("GRU Test Data", fontsize='30')
        plt.savefig("images/GRU-test-result.png")
        plt.show()

        # 输出模型性能指标
        def mape(y_true, y_pred):
            return np.mean(np.abs((y_pred - y_true) / y_true))

        print('训练集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_train_predict, y_train)))  # 平均绝对误差
        print(mean_squared_error(y_train_predict, y_train))  # 均方误差
        print(mape(y_train_predict, y_train))

        print('测试集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_test_predict, y_test)))
        print(mean_squared_error(y_test_predict, y_test))
        print(mape(y_test_predict, y_test))
        # 将评估结果写入文件的第1行，保留四位小数并以百分比形式呈现
        with open('eval_results.txt', 'r+') as file:
            lines = file.readlines()
            mae_test = sqrt(mean_absolute_error(y_test_predict, y_test))
            mse_test = mean_squared_error(y_test_predict, y_test)
            mape_test = mape(y_test_predict, y_test)
            formatted_mae_test = "{:.4f}%".format(mae_test * 100)
            formatted_mse_test = "{:.4f}%".format(mse_test * 100)
            formatted_mape_test = "{:.4f}%".format(mape_test * 100)
            lines.insert(1, f"GRU {formatted_mae_test} {formatted_mse_test} {formatted_mape_test}\n")
            file.seek(0)
            file.writelines(lines)

    def train_cnn_lstm_model(self):
        QMessageBox.question(self, 'YES', '正在训练CNN-LSTM模型，训练时间较长请耐心等待!', QMessageBox.Yes | QMessageBox.No,
                             QMessageBox.No)
        window = 6  # 时间窗口大小，用于定义输入数据的序列长度
        lstm_units = 16  # GRU层的神经元单元数量
        dropout = 0.01  # Dropout的概率
        epoch = 100  # 训练迭代次数

        '''
        读取数据
        '''
        # 读取数据文件 
        df00 = pd.read_excel('datasets/1min.xlsx')  # 读取数据
        df1 = df00.iloc[:, 0:10]  # 选择数据中的列范围

        # 打印数据的最后几行
        df1.tail()

        '''
        数据归一化
        '''
        min_max_scaler0 = preprocessing.MinMaxScaler()
        min_max_scaler1 = preprocessing.MinMaxScaler()
        df0 = min_max_scaler0.fit_transform(df1.iloc[:, 0:9])  # 对特征数据归一化
        df2 = min_max_scaler1.fit_transform(df1.iloc[:, 9:10])  # 对被预测量归一化
        df3 = np.concatenate((df0, df2), axis=1)
        df = pd.DataFrame(df3, columns=df1.columns)
        input_size = len(df.iloc[1, :])

        '''
        构建网络输入
        '''
        stock = df
        seq_len = window
        amount_of_features = len(stock.columns)
        print(amount_of_features)
        data = stock.values
        print(data.shape)
        sequence_length = seq_len + 1
        result = []

        # 创建时间序列数据，每个序列包含 seq_len+1 个数据点
        for index in range(len(data) - sequence_length):
            result.append(data[index: index + sequence_length])
        result = np.array(result)
        print(result.shape)
        row = round(0.5 * result.shape[0])
        print(row)
        train = result[:int(row), :]
        print(train.shape)
        x_train = train[:, :-1]

        y_train = train[:, -1][:, -1]
        print(y_train.shape)
        x_test = result[int(row):, :-1]
        print(x_test.shape)
        y_test = result[int(row):, -1][:, -1]

        '''
        搭建CNN-LSTM网络模型
        '''
        inputs = Input(shape=(window, input_size))
        model = Conv1D(filters=32, kernel_size=1, activation='tanh')(inputs)  # 卷积层
        model = MaxPooling1D(pool_size=window)(model)  # 池化层
        model = Dropout(dropout)(model)  # Dropout层

        model = LSTM(32, activation='tanh', name='LSTM')(model)  # GRU层
        outputs = Dense(1, activation='sigmoid')(model)
        model = Model(inputs=inputs, outputs=outputs)

        # 编译模型，定义损失函数和优化器
        model.compile(loss='mse', optimizer='RMSprop')

        # 打印模型结构
        model.summary()

        # 训练模型
        history = model.fit(x_train, y_train, 64, epoch, shuffle=False, validation_data=(x_test, y_test))

        # 绘制训练过程中的损失曲线
        loss = history.history['loss']
        val_loss = history.history['val_loss']
        plt.plot(loss, label='Train Loss')
        plt.legend(loc='upper right')
        plt.title('Train and Val Loss')
        plt.savefig("images/CNN-LSTM-train-loss.png")
        plt.show()

        # 在训练集上的拟合结果
        y_train_predict = model.predict(x_train)
        y_train_predict = y_train_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_train), pd.DataFrame(y_train_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)
        draw.iloc[0:, 0].plot(figsize=(12, 6))
        draw.iloc[0:, 1].plot(figsize=(12, 6))
        plt.legend(('real', 'predict'), fontsize='15')
        plt.title("CNN_LSTM Train Data", fontsize='30')
        plt.savefig("images/CNN-LSTM-train-result.png")
        plt.show()

        # 在测试集上的预测
        y_test_predict = model.predict(x_test)
        y_test_predict = y_test_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_test), pd.DataFrame(y_test_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)

        # 将测试集的结果保存为 'CNN_LSTM.csv' 文件
        draw.to_csv('results/CNN-LSTM模型测试集预测与真实结果对比.csv')
        model.save('model/CNN-LSTM.h5')

        # 绘制测试集的真实数据和预测结果
        fig, ax = plt.subplots(figsize=(10, 7))
        ax.plot(np.array(draw.iloc[:, 0]), label='Data', linestyle='--')
        ax.plot(np.array(draw.iloc[:, 1]), label='Prediction', linestyle='-')

        ax.set_xlabel('时间/分')
        ax.set_ylabel('光伏发电量/Kw')
        plt.title("CNN_LSTM Test Data", fontsize='30')
        plt.savefig("images/CNN-LSTM-test-result.png")
        plt.show()

        # 输出模型性能指标
        def mape(y_true, y_pred):
            return np.mean(np.abs((y_pred - y_true) / y_true))

        print('训练集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_train_predict, y_train)))  # 平均绝对误差
        print(mean_squared_error(y_train_predict, y_train))  # 均方误差
        print(mape(y_train_predict, y_train))  # 平均绝对误差

        print('测试集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_test_predict, y_test)))
        print(mean_squared_error(y_test_predict, y_test))
        print(mape(y_test_predict, y_test))
        # 将评估结果写入文件的第二行
        with open('eval_results.txt', 'r+') as file:
            lines = file.readlines()
            mae_test = sqrt(mean_absolute_error(y_test_predict, y_test))
            mse_test = mean_squared_error(y_test_predict, y_test)
            mape_test = mape(y_test_predict, y_test)
            formatted_mae_test = "{:.4f}%".format(mae_test * 100)
            formatted_mse_test = "{:.4f}%".format(mse_test * 100)
            formatted_mape_test = "{:.4f}%".format(mape_test * 100)
            lines.insert(2, f"CNN-LSTM {formatted_mae_test} {formatted_mse_test} {formatted_mape_test}\n")
            file.seek(0)
            file.writelines(lines)

    def train_cnn_gru_model(self):
        QMessageBox.question(self, 'YES', '正在训练CNN-GRU模型，训练时间较长请耐心等待!', QMessageBox.Yes | QMessageBox.No,
                             QMessageBox.No)
        window = 6  # 时间窗口大小，用于定义输入数据的序列长度
        gru_units = 16  # GRU层的神经元单元数量
        dropout = 0.01  # Dropout的概率
        epoch = 100  # 训练迭代次数

        '''
         读取数据
         '''
        # 读取数据文件
        df00 = pd.read_excel('datasets/1min.xlsx')  # 读取数据
        df1 = df00.iloc[:, 0:10]  # 选择数据中的列范围

        # 打印数据的最后几行
        df1.tail()

        '''
         数据归一化
         '''
        min_max_scaler0 = preprocessing.MinMaxScaler()
        min_max_scaler1 = preprocessing.MinMaxScaler()
        df0 = min_max_scaler0.fit_transform(df1.iloc[:, 0:9])  # 对特征数据归一化
        df2 = min_max_scaler1.fit_transform(df1.iloc[:, 9:10])  # 对被预测流量归一化
        df3 = np.concatenate((df0, df2), axis=1)
        df = pd.DataFrame(df3, columns=df1.columns)
        input_size = len(df.iloc[1, :])

        '''
         构建网络输入
         '''
        stock = df
        seq_len = window
        amount_of_features = len(stock.columns)
        print(amount_of_features)
        data = stock.values
        print(data.shape)
        sequence_length = seq_len + 1
        result = []

        # 创建时间序列数据，每个序列包含 seq_len+1 个数据点
        for index in range(len(data) - sequence_length):
            result.append(data[index: index + sequence_length])
        result = np.array(result)
        print(result.shape)
        row = round(0.5 * result.shape[0])
        print(row)
        train = result[:int(row), :]
        print(train.shape)
        x_train = train[:, :-1]

        y_train = train[:, -1][:, -1]
        print(y_train.shape)
        x_test = result[int(row):, :-1]
        print(x_test.shape)
        y_test = result[int(row):, -1][:, -1]

        '''
         搭建CNN-GRU网络模型
         '''
        inputs = Input(shape=(window, input_size))
        model = Conv1D(filters=32, kernel_size=1, activation='tanh')(inputs)  # 卷积层
        model = MaxPooling1D(pool_size=window)(model)  # 池化层
        model = Dropout(dropout)(model)  # Dropout层

        model = GRU(32, activation='tanh', name='GRU')(model)  # GRU层
        outputs = Dense(1, activation='sigmoid')(model)
        model = Model(inputs=inputs, outputs=outputs)

        # 编译模型，定义损失函数和优化器
        model.compile(loss='mse', optimizer='RMSprop')

        # 打印模型结构
        model.summary()

        # 训练模型
        history = model.fit(x_train, y_train, 64, epoch, shuffle=False, validation_data=(x_test, y_test))

        # 绘制训练过程中的损失曲线
        loss = history.history['loss']
        val_loss = history.history['val_loss']
        plt.plot(loss, label='Train Loss')
        plt.legend(loc='upper right')
        plt.title('CNN-GRU Train and Val Loss')
        plt.savefig("images/CNN-GRU-train-loss.png")
        plt.show()

        # 在训练集上的拟合结果
        y_train_predict = model.predict(x_train)
        y_train_predict = y_train_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_train), pd.DataFrame(y_train_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)
        draw.iloc[0:, 0].plot(figsize=(12, 6))
        draw.iloc[0:, 1].plot(figsize=(12, 6))
        plt.legend(('real', 'predict'), fontsize='15')
        plt.title("CNN-GRU Train Data", fontsize='30')
        plt.savefig("images/CNN-GRU-train-result.png")
        plt.show()

        # 在测试集上的预测
        y_test_predict = model.predict(x_test)
        y_test_predict = y_test_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_test), pd.DataFrame(y_test_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)

        # 将测试集的结果保存为 'CNN_GRU.csv' 文件
        draw.to_csv('results/CNN_GRU模型测试集预测与真实结果对比.csv')

        # 绘制测试集的真实数据和预测结果

        # 对横坐标轴进行标注
        fig, ax = plt.subplots(figsize=(10, 7))
        ax.plot(np.array(draw.iloc[:, 0]), label='Data', linestyle='--')
        ax.plot(np.array(draw.iloc[:, 1]), label='Prediction', linestyle='-')

        ax.set_xlabel('时间/分')
        ax.set_ylabel('光伏发电量/Kw')
        plt.title("Test Data", fontsize='30')
        plt.savefig("images/CNN-GRU-test-result.png")
        plt.show()

        # 输出模型性能指标
        def mape(y_true, y_pred):
            return np.mean(np.abs((y_pred - y_true) / y_true))

        print('训练集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_train_predict, y_train)))  # 平均绝对误差
        print(mean_squared_error(y_train_predict, y_train))  # 均方误差
        print(mape(y_train_predict, y_train))  # 平均绝对误差

        print('测试集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_test_predict, y_test)))
        print(mean_squared_error(y_test_predict, y_test))
        print(mape(y_test_predict, y_test))
        # 将评估结果写入文件的第二行
        with open('eval_results.txt', 'r+') as file:
            lines = file.readlines()
            mae_test = sqrt(mean_absolute_error(y_test_predict, y_test))
            mse_test = mean_squared_error(y_test_predict, y_test)
            mape_test = mape(y_test_predict, y_test)
            formatted_mae_test = "{:.4f}%".format(mae_test * 100)
            formatted_mse_test = "{:.4f}%".format(mse_test * 100)
            formatted_mape_test = "{:.4f}%".format(mape_test * 100)
            lines.insert(3, f"CNN-GRU {formatted_mae_test} {formatted_mse_test} {formatted_mape_test}\n")
            file.seek(0)
            file.writelines(lines)

    def train_LSTM_transform_model(self):
        QMessageBox.question(self, 'YES', '正在训练_LSTM_transformN模型，训练时间较长请耐心等待!', QMessageBox.Yes | QMessageBox.No,
                             QMessageBox.No)
        window = 6  # 时间窗口大小，用于定义输入数据的序列长度
        LSTM_units = 16  # LSTM层的神经元单元数量
        epoch = 100  # 训练迭代次数

        '''
        读取数据
        '''
        # 读取数据文件 'data16.csv'
        df00 = pd.read_excel('datasets/1min.xlsx')  # 读取数据
        df1 = df00.iloc[:, 0:10]  # 选择数据中的列范围

        # 打印数据的最后几行
        df1.tail()

        '''
        数据归一化
        '''
        min_max_scaler0 = preprocessing.MinMaxScaler()
        min_max_scaler1 = preprocessing.MinMaxScaler()
        df0 = min_max_scaler0.fit_transform(df1.iloc[:, 0:9])  # 对特征数据归一化
        df2 = min_max_scaler1.fit_transform(df1.iloc[:, 9:10])  # 对被预测流量归一化
        df3 = np.concatenate((df0, df2), axis=1)
        df = pd.DataFrame(df3, columns=df1.columns)
        input_size = len(df.iloc[1, :])

        '''
        构建网络输入
        '''
        stock = df
        seq_len = window
        amount_of_features = len(stock.columns)
        print(amount_of_features)
        data = stock.values
        print(data.shape)
        sequence_length = seq_len + 1
        result = []

        # 创建时间序列数据，每个序列包含 seq_len+1 个数据点
        for index in range(len(data) - sequence_length):
            result.append(data[index: index + sequence_length])
        result = np.array(result)
        print(result.shape)
        row = round(0.5 * result.shape[0])
        print(row)
        train = result[:int(row), :]
        print(train.shape)
        x_train = train[:, :-1]

        y_train = train[:, -1][:, -1]
        print(y_train.shape)
        x_test = result[int(row):, :-1]
        print(x_test.shape)
        y_test = result[int(row):, -1][:, -1]

        '''
        搭建LSTM+Transformer层网络模型
        '''
        # 定义LSTM网络模型

        window = 6  # 设置窗口大小
        input_size = 10  # 输入维度大小

        # 输入层
        inputs = Input(shape=(window, input_size))

        # LSTM层
        lstm_output = LSTM(32, activation='relu', return_sequences=True, name='LSTM0')(inputs)

        # Transformer层
        num_heads = 4  # 设置Transformer中的头数
        transformer_output = MultiHeadAttention(num_heads=num_heads, key_dim=64, value_dim=64)(lstm_output, lstm_output,
                                                                                               lstm_output)  # 传递三个相同的参数作为key, query, value
        transformer_output = LayerNormalization(epsilon=1e-6)(transformer_output)

        # 全连接层
        transformer_output = Flatten()(transformer_output)
        outputs = Dense(1, activation='sigmoid')(transformer_output)

        # 创建模型
        model = Model(inputs=inputs, outputs=outputs)

        # 编译模型，定义损失函数和优化器
        model.compile(loss='mse', optimizer='adam')

        # 打印模型结构
        model.summary()

        # 训练模型
        history = model.fit(x_train, y_train, 32, epoch, shuffle=False, validation_data=(x_test, y_test))

        # 绘制训练过程中的损失曲线
        loss = history.history['loss']
        val_loss = history.history['val_loss']
        plt.plot(loss, label='Train Loss')
        plt.legend(loc='upper right')
        plt.title('Train and Val Loss')
        plt.savefig("images/LSTM_transform-train-loss.png")
        plt.show()

        # 在训练集上的拟合结果
        y_train_predict = model.predict(x_train)
        y_train_predict = y_train_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_train), pd.DataFrame(y_train_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)
        draw.iloc[0:, 0].plot(figsize=(12, 6))
        draw.iloc[0:, 1].plot(figsize=(12, 6))
        plt.legend(('real', 'predict'), fontsize='15')
        plt.title("LSTM_transform Train Data", fontsize='30')
        plt.savefig("images/LSTM_transform-train-result.png")
        plt.show()

        # 在测试集上的预测
        y_test_predict = model.predict(x_test)
        y_test_predict = y_test_predict[:, 0]
        draw = pd.concat([pd.DataFrame(y_test), pd.DataFrame(y_test_predict)], axis=1)
        draw = min_max_scaler1.inverse_transform(draw)
        draw = DataFrame(draw)

        # 将测试集的结果保存为 'CNN_BiGRU_ATTENTION.csv' 文件
        draw.to_csv('results/LSTM_transform模型测试集预测与真实结果对比.csv')

        # 绘制测试集的真实数据和预测结果
        fig, ax = plt.subplots(figsize=(10, 7))
        ax.plot(np.array(draw.iloc[:, 0]), label='Data', linestyle='--')
        ax.plot(np.array(draw.iloc[:, 1]), label='Prediction', linestyle='-')

        ax.set_xlabel('时间/分')
        ax.set_ylabel('光伏发电量/Kw')
        plt.title("LSTM_transform Test Data", fontsize='30')
        plt.savefig("images/LSTM_transform-test-result.png")
        plt.show()

        # 输出模型性能指标
        def mape(y_true, y_pred):
            return np.mean(np.abs((y_pred - y_true) / y_true))

        print('训练集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_train_predict, y_train)))  # 平均绝对误差
        print(mean_squared_error(y_train_predict, y_train))  # 均方误差
        print(mape(y_train_predict, y_train))  # 平均绝对误差

        print('测试集上的MAE/MSE/MAPE')
        print(sqrt(mean_absolute_error(y_test_predict, y_test)))
        print(mean_squared_error(y_test_predict, y_test))
        print(mape(y_test_predict, y_test))
        with open('eval_results.txt', 'r+') as file:
            lines = file.readlines()
            mae_test = sqrt(mean_absolute_error(y_test_predict, y_test))
            mse_test = mean_squared_error(y_test_predict, y_test)
            mape_test = mape(y_test_predict, y_test)
            formatted_mae_test = "{:.4f}%".format(mae_test * 100)
            formatted_mse_test = "{:.4f}%".format(mse_test * 100)
            formatted_mape_test = "{:.4f}%".format(mape_test * 100)
            lines.insert(4, f"LSTM_transform {formatted_mae_test} {formatted_mse_test} {formatted_mape_test}\n")
            file.seek(0)
            file.writelines(lines)

    def show_eval_image(self):
        self.dataWidget.setVisible(False)
        self.tableWidget.setVisible(False)
        self.label_image.setVisible(True)

        x = pd.read_csv('./results/LSTM_transform模型测试集预测与真实结果对比.csv')
        x0 = pd.read_csv('./results/LSTM_transform模型测试集预测与真实结果对比.csv')
        x1 = pd.read_csv('./results/LSTM模型测试集预测与真实结果对比.csv')
        x2 = pd.read_csv('./results/GRU模型测试集预测与真实结果对比.csv')
        x3 = pd.read_csv('./results/CNN-LSTM模型测试集预测与真实结果对比.csv')
        x4 = pd.read_csv('./results/CNN_GRU模型测试集预测与真实结果对比.csv')
        fig, ax = plt.subplots(figsize=(8, 4))

        # 绘制主图
        ax.plot(np.array(x.iloc[:, 1:2]), label='真实值', linestyle='--', color='blue')
        ax.plot(np.array(x0.iloc[:, 2:3]), label='LSTM_Transformer', linestyle='-', color='red')
        ax.plot(np.array(x1.iloc[:, 2:3]), label='LSTM', linestyle=':', color='black')
        ax.plot(np.array(x2.iloc[:, 2:3]), label='GRU', linestyle='-.', color='green')
        ax.plot(np.array(x3.iloc[:, 2:3]), label='CNN-LSTM', linestyle='-', color='yellow')
        ax.plot(np.array(x4.iloc[:, 2:3]), label='CNN-GRU', linestyle='--', color='black')  # 修改为不同的线形
        ax.set_ylim(0, 8)
        ax.legend(loc='upper left')
        ax.set_xlabel('时间')
        ax.set_ylabel('数量')

        # 对横坐标轴进行标注

        plt.title('预测值与真实值')

        # 创建放大区域
        axins = inset_axes(ax, width="30%", height="30%", loc="upper right")
        axins.plot(np.array(x.iloc[:, 1:2]), linestyle='--', color='blue')
        axins.plot(np.array(x0.iloc[:, 2:3]), linestyle='-', color='red')
        axins.plot(np.array(x1.iloc[:, 2:3]), linestyle=':', color='black')
        axins.plot(np.array(x2.iloc[:, 2:3]), linestyle='-.', color='green')
        axins.plot(np.array(x3.iloc[:, 2:3]), linestyle='-', color='yellow')

        # 设置放大区域的坐标范围
        x1, x2, y1, y2 = 1400, 1600, 4, 5
        axins.set_xlim(x1, x2)
        axins.set_ylim(y1, y2)

        # 在主图中标记放大区域
        mark_inset(ax, axins, loc1=2, loc2=2, fc="none", ec="0.5")

        plt.savefig("./images/结果对比图.png", bbox_inches="tight", dpi=600)
        image = np.array(Image.open("images/结果对比图.png").convert("RGB").resize((900, 700)))
        showImage = QImage(image, image.shape[1], image.shape[0], 3 * image.shape[1], QImage.Format_RGB888)
        self.label_image.setPixmap(QPixmap.fromImage(showImage))  # 界面展示图片

    def show_eval_result(self):
        self.dataWidget.setVisible(False)
        self.label_image.setVisible(False)
        self.tableWidget.setVisible(True)

        # 从本地文件读取结果
        with open('eval_results.txt', 'r') as file:
            lines = file.readlines()

        # 用文件中的数据填充表格
        for i, line in enumerate(lines):
            items = line.split()
            for j, item in enumerate(items):
                self.tableWidget.setItem(i, j, QTableWidgetItem(item))

    def paintEvent(self, QPaintEvent):
        qp = QPainter()
        qp.begin(self)
        self.drawLines(qp)
        qp.end()

    @staticmethod
    def drawLines(qp):
        """
        画边框
        """
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        qp.setPen(pen)
        qp.drawLine(10, 10, 1400, 10)
        qp.drawLine(1400, 10, 1400, 850)
        qp.drawLine(1400, 850, 10, 850)
        qp.drawLine(10, 850, 10, 10)

    def close_demo(self):
        self.close()
        sys.exit()


class LoginGui(QWidget):

    def __init__(self):
        super(LoginGui, self).__init__()
        self.resize(1420, 900)  # 界面的大小
        self.setWindowTitle("windows")  # 界面的title

        self.ui_p = PyQtGui()
        # ui_p.show()

        Image.open("images/background.jpg").resize((1420, 900)).save("images/background.jpg")
        palette = QPalette()
        palette.setBrush(QPalette.Background, QBrush(QPixmap("images/background.jpg")))
        self.setPalette(palette)

        self.label_title = QLabel(self)
        self.label_title.setText("登录界面")
        self.label_title.setStyleSheet("color:black")
        self.label_title.setFont(QFont("Microsoft YaHei", 40, 50))
        self.label_title.move(530, 30)

        self.btn_username = QPushButton(self)
        self.btn_username.setText("用户名:")
        self.btn_username.setFixedSize(200, 80)
        self.btn_username.setFont(QFont("Microsoft YaHei", 23, 50))
        self.btn_username.move(300, 200)

        self.edit_username = QLineEdit(self)
        self.edit_username.setPlaceholderText("请输入用户名")  # 设置占位文本
        self.edit_username.setFixedSize(400, 80)
        self.edit_username.setFont(QFont("Microsoft YaHei", 23, 50))
        self.edit_username.move(600, 200)
        self.edit_username.mousePressEvent = self.clear_username_placeholder

        self.btn_password = QPushButton(self)
        self.btn_password.setText("密码:")
        self.btn_password.setFixedSize(200, 80)
        self.btn_password.setFont(QFont("Microsoft YaHei", 23, 50))
        self.btn_password.move(300, 300)

        self.edit_password = QLineEdit(self)
        self.edit_password.setPlaceholderText("请输入密码")  # 设置占位文本
        self.edit_password.setFixedSize(400, 80)
        self.edit_password.setFont(QFont("Microsoft YaHei", 23, 50))
        self.edit_password.move(600, 300)
        self.edit_password.mousePressEvent = self.clear_password_placeholder

        self.btn_login = QPushButton(self)
        self.btn_login.setFixedSize(200, 100)
        self.btn_login.setText("登陆")
        self.btn_login.setFont(QFont("Microsoft YaHei", 30, 50))
        self.btn_login.move(350, 500)
        self.btn_login.clicked.connect(self.login)

        self.btn_register = QPushButton(self)
        self.btn_register.setFixedSize(200, 100)
        self.btn_register.setText("注册")
        self.btn_register.setFont(QFont("Microsoft YaHei", 30, 50))
        self.btn_register.move(700, 500)
        self.btn_register.clicked.connect(self.register)

        self.btn_return = QPushButton(self)
        self.btn_return.setFixedSize(200, 100)
        self.btn_return.setText("返回")
        self.btn_return.setFont(QFont("Microsoft YaHei", 30, 50))
        self.btn_return.move(700, 500)
        self.btn_return.setVisible(False)
        self.btn_return.clicked.connect(self.return_login)

        self.btn_register2 = QPushButton(self)
        self.btn_register2.setFixedSize(200, 100)
        self.btn_register2.setText("注册")
        self.btn_register2.setFont(QFont("Microsoft YaHei", 30, 50))
        self.btn_register2.move(350, 500)
        self.btn_register2.clicked.connect(self.save_info)
        self.btn_register2.setVisible(False)

    def return_login(self):
        self.btn_return.setVisible(False)
        self.btn_login.setVisible(True)
        self.btn_register2.setVisible(False)
        self.btn_register.setVisible(True)

    def clear_username_placeholder(self, event):
        self.edit_username.clear()
        QLineEdit.mousePressEvent(self.edit_username, event)

    def clear_password_placeholder(self, event):
        self.edit_password.clear()
        self.edit_password.setEchoMode(QLineEdit.Password)
        QLineEdit.mousePressEvent(self.edit_password, event)

    def login(self):
        data = pd.read_csv("datasets/user_info.txt", dtype=str)
        usernames = data["username"].values.tolist()
        passwords = data["password"].values.tolist()
        username = self.edit_username.text()
        password = self.edit_password.text()

        if not username or not password:
            QMessageBox.warning(self, '警告', '账户密码不能为空，请重新输入！', QMessageBox.Ok)
            return

        if username in usernames and password in passwords:
            reply = QMessageBox.question(self, 'YES', '登陆成功!', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.ui_p.show()
                self.close()
        else:
            QMessageBox.warning(self, '警告', '用户名或密码错误！', QMessageBox.Ok)

    def save_info(self):
        data = pd.read_csv("datasets/user_info.txt", dtype=str)
        usernames = data["username"].values.tolist()
        username = self.edit_username.text()
        password = self.edit_password.text()

        if username == "" or password == "":
            QMessageBox.question(self, '提示', '用户名或密码不能为空，请重新输入!', QMessageBox.Yes)
            return  # 如果用户名或密码为空，则返回，不继续执行保存信息的操作

        if username in usernames:
            QMessageBox.question(self, '提示', '用户名已经存在，请重新输入!', QMessageBox.Yes)
        else:
            with open("datasets/user_info.txt", "a", encoding="utf-8") as f:
                f.write(f"{username},{password}" + "\n")
                QMessageBox.question(self, '提示', '注册成功!', QMessageBox.Yes)

    def register(self):
        self.btn_return.setVisible(True)
        self.btn_login.setVisible(False)
        self.btn_register.setVisible(False)
        self.btn_register2.setVisible(True)

    @staticmethod
    def drawLines(qp):
        """
        画边框
        """
        pen = QPen(Qt.black, 2, Qt.SolidLine)
        qp.setPen(pen)
        qp.drawLine(10, 10, 1400, 10)
        qp.drawLine(1400, 10, 1400, 850)
        qp.drawLine(1400, 850, 10, 850)
        qp.drawLine(10, 850, 10, 10)

    def close_demo(self):
        self.close()
        sys.exit()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui_login = LoginGui()
    ui_login.show()
    # ui_p = PyQtGui()
    # ui_p.show()
    sys.exit(app.exec_())
